// Copyright (c) 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include <cerrno>

#include "base/files/file.h"
#include "base/files/file_path.h"
#include "base/files/scoped_temp_dir.h"
#include "base/macros.h"
#include "base/memory/ptr_util.h"
#include "base/strings/string16.h"
#include "base/strings/utf_string_conversions.h"
#include "content/browser/indexed_db/indexed_db_backing_store.h"
#include "content/browser/indexed_db/indexed_db_data_loss_info.h"
#include "content/browser/indexed_db/leveldb/leveldb_database.h"
#include "content/browser/indexed_db/leveldb/mock_leveldb_factory.h"
#include "net/url_request/url_request_context_getter.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/leveldatabase/env_chromium.h"

using base::StringPiece;
using content::IndexedDBBackingStore;
using content::LevelDBComparator;
using content::LevelDBDatabase;
using content::LevelDBFactory;
using content::LevelDBSnapshot;
using testing::_;
using testing::Exactly;
using testing::Invoke;

namespace base {
class TaskRunner;
}

namespace content {
class IndexedDBFactory;
}

namespace {

class BustedLevelDBDatabase : public LevelDBDatabase {
public:
    BustedLevelDBDatabase() { }
    static std::unique_ptr<LevelDBDatabase> Open(
        const base::FilePath& file_name,
        const LevelDBComparator* /*comparator*/)
    {
        return base::MakeUnique<BustedLevelDBDatabase>();
    }
    leveldb::Status Get(const base::StringPiece& key,
        std::string* value,
        bool* found,
        const LevelDBSnapshot* = 0) override
    {
        return leveldb::Status::IOError("It's busted!");
    }

private:
    DISALLOW_COPY_AND_ASSIGN(BustedLevelDBDatabase);
};

class BustedLevelDBFactory : public LevelDBFactory {
public:
    leveldb::Status OpenLevelDB(const base::FilePath& file_name,
        const LevelDBComparator* comparator,
        std::unique_ptr<LevelDBDatabase>* db,
        bool* is_disk_full = 0) override
    {
        if (open_error_.ok())
            *db = BustedLevelDBDatabase::Open(file_name, comparator);
        return open_error_;
    }
    leveldb::Status DestroyLevelDB(const base::FilePath& file_name) override
    {
        return leveldb::Status::IOError("error");
    }
    void SetOpenError(const leveldb::Status& open_error)
    {
        open_error_ = open_error;
    }

private:
    leveldb::Status open_error_;
};

TEST(IndexedDBIOErrorTest, CleanUpTest)
{
    content::IndexedDBFactory* factory = NULL;
    const url::Origin origin(GURL("http://localhost:81"));
    base::ScopedTempDir temp_directory;
    ASSERT_TRUE(temp_directory.CreateUniqueTempDir());
    const base::FilePath path = temp_directory.GetPath();
    scoped_refptr<net::URLRequestContextGetter> request_context_getter;

    BustedLevelDBFactory busted_factory;
    content::MockLevelDBFactory mock_leveldb_factory;
    ON_CALL(mock_leveldb_factory, OpenLevelDB(_, _, _, _)).WillByDefault(Invoke(&busted_factory, &BustedLevelDBFactory::OpenLevelDB));
    ON_CALL(mock_leveldb_factory, DestroyLevelDB(_)).WillByDefault(Invoke(&busted_factory, &BustedLevelDBFactory::DestroyLevelDB));

    EXPECT_CALL(mock_leveldb_factory, OpenLevelDB(_, _, _, _)).Times(Exactly(1));
    EXPECT_CALL(mock_leveldb_factory, DestroyLevelDB(_)).Times(Exactly(1));
    content::IndexedDBDataLossInfo data_loss_info;
    bool disk_full = false;
    base::SequencedTaskRunner* task_runner = NULL;
    bool clean_journal = false;
    leveldb::Status s;
    scoped_refptr<IndexedDBBackingStore> backing_store = IndexedDBBackingStore::Open(
        factory, origin, path, request_context_getter, &data_loss_info,
        &disk_full, &mock_leveldb_factory, task_runner, clean_journal, &s);
}

TEST(IndexedDBNonRecoverableIOErrorTest, NuancedCleanupTest)
{
    content::IndexedDBFactory* factory = NULL;
    const url::Origin origin(GURL("http://localhost:81"));
    scoped_refptr<net::URLRequestContextGetter> request_context_getter;
    base::ScopedTempDir temp_directory;
    ASSERT_TRUE(temp_directory.CreateUniqueTempDir());
    const base::FilePath path = temp_directory.GetPath();
    content::IndexedDBDataLossInfo data_loss_info;
    bool disk_full = false;
    base::SequencedTaskRunner* task_runner = NULL;
    bool clean_journal = false;
    leveldb::Status s;

    BustedLevelDBFactory busted_factory;
    content::MockLevelDBFactory mock_leveldb_factory;
    ON_CALL(mock_leveldb_factory, OpenLevelDB(_, _, _, _)).WillByDefault(Invoke(&busted_factory, &BustedLevelDBFactory::OpenLevelDB));
    ON_CALL(mock_leveldb_factory, DestroyLevelDB(_)).WillByDefault(Invoke(&busted_factory, &BustedLevelDBFactory::DestroyLevelDB));

    EXPECT_CALL(mock_leveldb_factory, OpenLevelDB(_, _, _, _)).Times(Exactly(4));
    EXPECT_CALL(mock_leveldb_factory, DestroyLevelDB(_)).Times(Exactly(0));

    busted_factory.SetOpenError(MakeIOError("some filename", "some message",
        leveldb_env::kNewLogger,
        base::File::FILE_ERROR_NO_SPACE));
    scoped_refptr<IndexedDBBackingStore> backing_store = IndexedDBBackingStore::Open(
        factory, origin, path, request_context_getter, &data_loss_info,
        &disk_full, &mock_leveldb_factory, task_runner, clean_journal, &s);
    ASSERT_TRUE(s.IsIOError());

    busted_factory.SetOpenError(MakeIOError("some filename",
        "some message",
        leveldb_env::kNewLogger,
        base::File::FILE_ERROR_NO_MEMORY));
    scoped_refptr<IndexedDBBackingStore> backing_store2 = IndexedDBBackingStore::Open(
        factory, origin, path, request_context_getter, &data_loss_info,
        &disk_full, &mock_leveldb_factory, task_runner, clean_journal, &s);
    ASSERT_TRUE(s.IsIOError());

    busted_factory.SetOpenError(MakeIOError("some filename", "some message",
        leveldb_env::kNewLogger,
        base::File::FILE_ERROR_IO));
    scoped_refptr<IndexedDBBackingStore> backing_store3 = IndexedDBBackingStore::Open(
        factory, origin, path, request_context_getter, &data_loss_info,
        &disk_full, &mock_leveldb_factory, task_runner, clean_journal, &s);
    ASSERT_TRUE(s.IsIOError());

    busted_factory.SetOpenError(MakeIOError("some filename",
        "some message",
        leveldb_env::kNewLogger,
        base::File::FILE_ERROR_FAILED));
    scoped_refptr<IndexedDBBackingStore> backing_store4 = IndexedDBBackingStore::Open(
        factory, origin, path, request_context_getter, &data_loss_info,
        &disk_full, &mock_leveldb_factory, task_runner, clean_journal, &s);
    ASSERT_TRUE(s.IsIOError());
}

} // namespace
